#!/bin/bash
#/ Usage: webvirtcloud.sh [-vh]
#/
#/ Install Webvirtcloud virtualization web interface.
#/
#/ OPTIONS:
#/   -v | --verbose    Enable verbose output.
#/   -h | --help       Show this message.

########################################################
#            Webvirtcloud Install Script               #
# Script created by Mike Tucker(mtucker6784@gmail.com) #
#              adapted by catborise                    #
#              catborise@gmail.com                     #
#                                                      #
#  Feel free to modify, but please give                #
#  credit where it's due. Thanks!                      #
########################################################

# Parse arguments
while true; do
  case "$1" in
    -h|--help)
      show_help=true
      shift
      ;;
    -v|--verbose)
      set -x
      verbose=true
      shift
      ;;
    -*)
      echo "Error: invalid argument: '$1'" 1>&2
      exit 1
      ;;
    *)
      break
      ;;
  esac
done

print_usage () {
  grep '^#/' <"$0" | cut -c 4-
  exit 1
}

if [ -n "$show_help" ]; then
  print_usage
else
  for x in "$@"; do
    if [ "$x" = "--help" ] || [ "$x" = "-h" ]; then
      print_usage
    fi
  done
fi

# ensure running as root
if [ "$(id -u)" != "0" ]; then
    #Debian doesnt have sudo if root has a password.
    if ! hash sudo 2>/dev/null; then
        exec su -c "$0" "$@"
    else
        exec sudo "$0" "$@"
    fi
fi

clear

readonly APP_USER="wvcuser"
readonly APP_REPO_URL="https://github.com/retspen/webvirtcloud.git"
readonly APP_NAME="webvirtcloud"
readonly APP_PATH="/srv/$APP_NAME"

readonly PYTHON="python3"

progress () {
  spin[0]="-"
  spin[1]="\\"
  spin[2]="|"
  spin[3]="/"

  echo -n " "
  while kill -0 "$pid" > /dev/null 2>&1; do
    for i in "${spin[@]}"; do
      echo -ne "\\b$i"
      sleep .3
    done
  done
  echo ""
}

log () {
  if [ -n "$verbose" ]; then
    eval "$@" |& tee -a /var/log/webvirtcloud-install.log
  else
    eval "$@" |& tee -a /var/log/webvirtcloud-install.log >/dev/null 2>&1
  fi
}

install_packages () {
  case $distro in
    ubuntu|debian)
      for p in $PACKAGES; do
        if dpkg -s "$p" >/dev/null 2>&1; then
          echo "  * $p already installed"
        else
          echo "  * Installing $p"
          log "DEBIAN_FRONTEND=noninteractive apt-get install -y $p"
        fi
      done;
      ;;
    centos)
      for p in $PACKAGES; do
        if yum list installed "$p" >/dev/null 2>&1; then
          echo "  * $p already installed"
        else
          echo "  * Installing $p"
          log "yum -y install $p"
        fi
      done;
      ;;
    fedora|openEuler)
      for p in $PACKAGES; do
        if dnf list installed "$p" >/dev/null 2>&1; then
          echo "  * $p already installed"
        else
          echo "  * Installing $p"
          log "dnf -y install $p"
        fi
      done;
      ;;
    uos)
      if test "${codename}" == "eagle"; then
        check_package_cmd=("dpkg" "-s")
        install_package_cmd="apt-get install -y"
      else
        check_package_cmd=("dnf" "list" "installed")
        install_package_cmd="dnf -y install"
      fi
      for p in $PACKAGES; do
        # shellcheck disable=SC2048
        if ${check_package_cmd[*]} "$p" >/dev/null 2>&1; then
          echo "  * $p already installed"
        else
          echo "  * Installing $p"
          log "${install_package_cmd} $p"
        fi
      done
      ;;
  esac
}

configure_nginx () {
  # Remove default configuration 
  rm /etc/nginx/nginx.conf
  if [ -f /etc/nginx/sites-enabled/default ]; then
    rm /etc/nginx/sites-enabled/default
  fi

  chown -R "$nginx_group":"$nginx_group" /var/lib/nginx
  # Copy new configuration and webvirtcloud.conf
  echo "  * Copying Nginx configuration"
  local nginx_template_conf
  nginx_template_conf="${APP_PATH}/conf/nginx/${distro}_${codename}_nginx.conf"
  if ! test -f "${nginx_template_conf}"; then
    nginx_template_conf="${APP_PATH}/conf/nginx/${distro}_nginx.conf"
  fi
  cp "${nginx_template_conf}" /etc/nginx/nginx.conf
  cp "$APP_PATH"/conf/nginx/webvirtcloud.conf /etc/nginx/conf.d/

  if [ -n "$fqdn" ]; then
     fqdn_escape="$(echo -n "$fqdn"|sed -e 's/[](){}<>=:\!\?\+\|\/\&$*.^[]/\\&/g')"
     sed -i "s|\\(#server_name\\).*|server_name $fqdn_escape;|" "$nginxfile"
  fi

  novncd_port_escape="$(echo -n "$novncd_port"|sed -e 's/[](){}<>=:\!\?\+\|\/\&$*.^[]/\\&/g')"
  sed -i "s|server 127.0.0.1:6080;|server 127.0.0.1:$novncd_port_escape;|" "$nginxfile"

}

configure_supervisor () {
  # Copy template supervisor service for gunicorn and novnc
  echo "  * Copying supervisor configuration"
  mkdir -p "$supervisor_conf_path" > /dev/null 2>&1
  cp "$APP_PATH"/conf/supervisor/webvirtcloud.conf "$supervisor_conf_path"/"$supervisor_file_name"
  nginx_group_escape="$(echo -n "$nginx_group"|sed -e 's/[](){}<>=:\!\?\+\|\/\&$*.^[]/\\&/g')"
  sed -i "s|^\\(user=\\).*|\\1$nginx_group_escape|" "$supervisor_conf_path/$supervisor_file_name"
}

create_user () {
  echo "* Creating webvirtcloud user."

  if [ "$distro" == "ubuntu" ] || [ "$distro" == "debian" ] ||
    [[ "$distro" == "uos" && "$codename" == "eagle" ]]; then
    adduser --quiet --disabled-password --gecos '""' "$APP_USER"
  else
    adduser "$APP_USER"
  fi

  usermod -a -G "$nginx_group" "$APP_USER"
  usermod -a -G libvirt "$nginx_group"
}

run_as_app_user () {
  if ! hash sudo 2>/dev/null; then
      su -c "$@" "$APP_USER"
  else
      sudo -i -u "$APP_USER" "$@"
  fi
}

check_python () {
  # check if python3 is installed.
  if ! hash "$PYTHON" 2>/dev/null; then
    echo "Python3 is not installed. Please install Python3 and try again."
    exit 1
  fi

  # check if python3 version is grater than 3.10 amd set it as default
  if ! "$PYTHON" -c 'import sys; assert sys.version_info >= (3, 10)' >/dev/null 2>&1; then
    echo "Your Python version is less than 3.10. This script requires Python 3.10 or greater."
    echo "Please install Python 3.10 or greater and set it as the default version."
    echo "Use update-alternatives command to set default python version to latest."
    echo "For example: sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1"
    echo "Then run this script again."
    echo "Do not forget to install pip3 and python3-devel for python3.10 or later."
    exit 1
  fi

  # check if pip3 is installed
  if ! hash pip3 2>/dev/null; then
    echo "pip3 is not installed. Please install pip3 and try again."
    exit 1
  fi
}

activate_python_environment () {
    cd "$APP_PATH" || exit
    # Check if virtualenv is installed
    if ! "$PYTHON" -c 'import virtualenv' >/dev/null 2>&1; then
      echo "Virtualenv is not installed. Please install virtualenv and try again."
      exit 1
    fi
    # Create a virtual environment
    echo "* Creating virtual environment in $APP_PATH/venv"
    virtualenv -p "$PYTHON" venv
    # shellcheck disable=SC1091
    source venv/bin/activate
}

generate_secret_key() {
  "$PYTHON" - <<END
import random
print(''.join(random.SystemRandom().choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)))
END
}


install_webvirtcloud () {
  create_user
 
  echo "* Cloning $APP_NAME from github to the web directory."
  log "git clone $APP_REPO_URL $APP_PATH"

  echo "* Configuring settings.py file."
  cp "$APP_PATH/webvirtcloud/settings.py.template" "$APP_PATH/webvirtcloud/settings.py"
  
  secret_key=$(generate_secret_key)
  echo "* Secret for Django generated: $secret_key"
  tzone_escape="$(echo -n "$tzone"|sed -e 's/[](){}<>=:\!\?\+\|\/\&$*.^[]/\\&/g')"
  secret_key_escape="$(echo -n "$secret_key"|sed -e 's/[](){}<>=:\!\?\+\|\/\&$*.^[]/\\&/g')"
  novncd_port_escape="$(echo -n "$novncd_port"|sed -e 's/[](){}<>=:\!\?\+\|\/\&$*.^[]/\\&/g')"
  novncd_public_port_escape="$(echo -n "$novncd_public_port"|sed -e 's/[](){}<>=:\!\?\+\|\/\&$*.^[]/\\&/g')"
  novncd_host_escape="$(echo -n "$novncd_host"|sed -e 's/[](){}<>=:\!\?\+\|\/\&$*.^[]/\\&/g')"

  #TODO escape SED delimiter in variables
  sed -i "s|^\\(TIME_ZONE = \\).*|\\1$tzone_escape|" "$APP_PATH/webvirtcloud/settings.py"
  sed -i "s|^\\(SECRET_KEY = \\).*|\\1\'$secret_key_escape\'|" "$APP_PATH/webvirtcloud/settings.py"
  sed -i "s|^\\(WS_PORT = \\).*|\\1$novncd_port_escape|" "$APP_PATH/webvirtcloud/settings.py"
  sed -i "s|^\\(WS_PUBLIC_PORT = \\).*|\\1$novncd_public_port_escape|" "$APP_PATH/webvirtcloud/settings.py"
  sed -i "s|^\\(WS_HOST = \\).*|\\1\'$novncd_host_escape\'|" "$APP_PATH/webvirtcloud/settings.py"

  # set CSRF TRUSTED ORIGINS
  host_ip="'http://127.0.0.1', "
  for i in $(hostname -I); do
    host_ip+="'http://$i', " 
  done
  sed -i "s|^\\(CSRF_TRUSTED_ORIGINS = \\).*|\\1\[ \'http://$fqdn\', $host_ip ]|" /srv/webvirtcloud/webvirtcloud/settings.py

  echo "* Checking up Python3 version."
  check_python

  echo "* Activate virtual environment."
  activate_python_environment

  echo "* Install App's Python requirements."
  pip3 install -U pip
  pip3 install -r conf/requirements.txt -q

  

  chown -R "$nginx_group":"$nginx_group" "$APP_PATH"

  
  echo "* Django Migrate."
  log "$PYTHON $APP_PATH/manage.py migrate"
  $PYTHON $APP_PATH/manage.py makemigrations
  $PYTHON $APP_PATH/manage.py migrate
  
  
  echo "* Django Collect Static"
  log "$PYTHON $APP_PATH/manage.py collectstatic --noinput"
  $PYTHON $APP_PATH/manage.py collectstatic --noinput
  
  chown -R "$nginx_group":"$nginx_group" "$APP_PATH"
}

set_firewall () {
  if test -n "$(command -v firewall-cmd)" && test "$(firewall-cmd --state)" == "running"; then
    echo "* Configuring firewall to allow HTTP & novnc traffic."
    log "firewall-cmd --zone=public --add-port=http/tcp --permanent"
    log "firewall-cmd --zone=public --add-port=$novncd_port/tcp --permanent"
    #firewall-cmd --zone=public --add-port=$novncd_port/tcp --permanent
    log "firewall-cmd --zone=public --add-port=$novncd_public_port/tcp --permanent"
    #firewall-cmd --zone=public --add-port=$novncd_public_port/tcp --permanent
    log "firewall-cmd --reload"
    #firewall-cmd --reload
  fi
}

set_selinux () {
  #Check if SELinux is enforcing
  if [ "$(getenforce)" == "Enforcing" ]; then
    echo "* Configuring SELinux."
    #Sets SELinux context type so that scripts running in the web server process are allowed read/write access
    chcon -R -h -t httpd_sys_rw_content_t "$APP_PATH/"
    setsebool -P httpd_can_network_connect 1
  fi
}

set_hosts () {
  echo "* Setting up hosts file."
  echo >> /etc/hosts "127.0.0.1 $(hostname) $fqdn"
}

restart_supervisor () {
  echo "* Setting Supervisor to start on boot and restart."
  log "systemctl enable $supervisor_service"
  #systemctl enable $supervisor_service
  log "systemctl restart $supervisor_service"
  #systemctl restart $supervisor_service
}

restart_nginx () {
  echo "* Setting Nginx to start on boot and starting Nginx."
  log "systemctl enable nginx.service"
  #systemctl enable nginx.service
  log "systemctl restart nginx.service"
  #systemctl restart nginx.service
}


if [[ -f /etc/lsb-release || -f /etc/debian_version ]]; then
  distro="$(lsb_release -is)"
  version="$(lsb_release -rs)"
  codename="$(lsb_release -cs)"
elif [ -f /etc/os-release ]; then
  # shellcheck disable=SC1091
  distro="$(source /etc/os-release && echo "$ID")"
  # shellcheck disable=SC1091
  version="$(source /etc/os-release && echo "$VERSION_ID")"
  #Order is important here.  If /etc/os-release and /etc/centos-release exist, we're on centos 7.
  #If only /etc/centos-release exist, we're on centos6(or earlier).  Centos-release is less parsable,
  #so lets assume that it's version 6 (Plus, who would be doing a new install of anything on centos5 at this point..)
  #/etc/os-release properly detects fedora
elif [ -f /etc/centos-release ]; then
  distro="centos"
  version="8"
else
  distro="unsupported"
fi


echo '
      WEBVIRTCLOUD
'

echo "" 
echo "  Welcome to Webvirtcloud Installer for RHEL Based OSes, Debian and Ubuntu!"
echo ""
shopt -s nocasematch
case $distro in
  *ubuntu*)
    echo "  The installer has detected $distro version $version codename $codename."
    distro=ubuntu
    nginx_group=www-data
    nginxfile=/etc/nginx/conf.d/$APP_NAME.conf
    supervisor_service=supervisord
    supervisor_conf_path=/etc/supervisor/conf.d
    supervisor_file_name=webvirtcloud.conf
    ;;
  *debian*)
    echo "  The installer has detected $distro version $version codename $codename."
    distro=debian
    nginx_group=www-data
    nginxfile=/etc/nginx/conf.d/$APP_NAME.conf
    supervisor_service=supervisor
    supervisor_conf_path=/etc/supervisor/conf.d
    supervisor_file_name=webvirtcloud.conf
    ;;
  *centos*|*redhat*|*ol*|*rhel*|*rocky*|*Rocky*|*alma*)
    echo "  The installer has detected $distro version $version."
    distro=centos
    nginx_group=nginx
    nginxfile=/etc/nginx/conf.d/$APP_NAME.conf
    supervisor_service=supervisord
    supervisor_conf_path=/etc/supervisord.d
    supervisor_file_name=webvirtcloud.ini
    ;;
  *Uos*|*uos*)
    # codename may be fuyu, kongzi, eagle or empty string.
    output_expand=""
    if test -n "${codename}"; then
      output_expand=" codename ${codename}"
    fi
    echo "  The installer has detected $distro version $version${output_expand}."
    distro=uos
    nginxfile=/etc/nginx/conf.d/$APP_NAME.conf
    if test "${codename}" == "eagle"; then
      nginx_group=www-data
      supervisor_service=supervisor
      supervisor_conf_path=/etc/supervisor/conf.d
      supervisor_file_name=webvirtcloud.conf
    else
      nginx_group=nginx
      supervisor_service=supervisord
      supervisor_conf_path=/etc/supervisord.d
      supervisor_file_name=webvirtcloud.ini
    fi
    ;;
  *OpenEuler*|*openEuler*)
    echo "  The installer has detected $distro version $version."
    distro=openEuler
    nginx_group=nginx
    nginxfile=/etc/nginx/conf.d/$APP_NAME.conf
    supervisor_service=supervisord
    supervisor_conf_path=/etc/supervisord.d
    supervisor_file_name=webvirtcloud.ini
    ;;
  *)
    echo "  The installer was unable to determine your OS. Exiting for safety."
    exit 1
    ;;
esac

fqdn="localhost"
setupfqdn=default
until [[ $setupfqdn == "yes" ]] || [[ $setupfqdn == "no" ]]; do
  echo -n "  Q. Do you want to configure fqdn for Nginx? (y/n) "
  read -r setupfqdn

  case $setupfqdn in
    [yY] | [yY][Ee][Ss] )
    fqdn=$(hostname --fqdn)
    echo -n "  Q. What is the FQDN of your server? ($fqdn): "
      read -r fqdn_from_user
      setupfqdn="yes"

      if [ ! -z $fqdn_from_user ]; then
        fqdn=$fqdn_from_user
      fi

      echo "     Setting to $fqdn"
      echo ""
      ;;
    [nN] | [n|N][O|o] )
      setupfqdn="no"
      ;;
    *)  echo "  Invalid answer. Please type y or n"
      ;;
  esac
done

echo -n "  Q. NOVNC service port number?(Default: 6080) "
read -r novncd_port
if [ -z "$novncd_port" ]; then
  readonly novncd_port=6080
fi
echo "     Setting novnc service port $novncd_port"
echo ""

echo -n "  Q. NOVNC public port number for reverse proxy(e.g: 80 or 443)?(Default: 6080) "
read -r novncd_public_port
if [ -z "$novncd_public_port" ]; then
  readonly novncd_public_port=6080
fi
echo "     Setting novnc public port $novncd_public_port"
echo ""

echo -n "  Q. NOVNC host listen ip?(Default: 0.0.0.0) "
read -r novncd_host
if [ -z "$novncd_host" ]; then
  readonly novncd_host="0.0.0.0"
fi
echo "     Setting novnc host ip $novncd_host"
echo ""

echo "========="
echo "distro: ${distro}"
echo "========="
case $distro in
  debian)
  if [[ "$version" -ge 9 ]]; then
    # Install for Debian 9.x / 10.x / 12.x
    tzone=\'$(cat /etc/timezone)\'

    echo -n "* Updating installed packages."
    log "apt-get update && apt-get -y upgrade" & pid=$!
    progress

    echo "*  Installing OS requirements."
    PACKAGES="git virtualenv python3-virtualenv python3-dev python3-lxml libvirt-dev zlib1g-dev libxslt1-dev libsasl2-dev libldap2-dev nginx smbios-utils libsasl2-modules gcc pkg-config python3-guestfs uuid"
    
    install_packages
    
    set_hosts

    install_webvirtcloud

    echo "* Configuring Nginx."
    configure_nginx

    echo "* Configuring Supervisor."
    log "pip install supervisor "
    configure_supervisor

    restart_supervisor
    restart_nginx
  fi
  ;;
  ubuntu)
  if [ "$version" == "18.04" ] || [ "$version" == "20.04" ] || [ "$version" == "22.04" ]; then
    # Install for Ubuntu 18 / 20
    tzone=\'$(cat /etc/timezone)\'

    echo -n "* Updating installed packages."
    log "apt-get update && apt-get -y upgrade" & pid=$!
    progress

    echo "*  Installing OS requirements."
    PACKAGES="git virtualenv python3-virtualenv python3-pip python3-dev python3-lxml libvirt-dev zlib1g-dev libxslt1-dev nginx libsasl2-modules gcc pkg-config python3-guestfs"
    install_packages

    set_hosts

    install_webvirtcloud

    echo "* Configuring Nginx."
    configure_nginx

    echo "* Configuring Supervisor."
    log "pip install supervisor "
    configure_supervisor

    restart_supervisor
    restart_nginx

  fi  
  ;;
  centos)
  if [[ "$version" =~ ^10  ]]; then
    # Install for CentOS/Redhat 10
    tzone=\'$(timedatectl|grep "Time zone"| awk '{print $3}')\'

    if command -v "crb" >/dev/null 2>&1; then
      echo "* CRB Repo is found, enabling it."
      log "crb enable > /dev/null 2>&1"
    else
      echo "* Enabling CRB repository."
      log "dnf config-manager --set-enabled crb"
    fi

    echo "* Adding wget & epel-release & supervisor repository."
    log "yum -y install wget epel-release supervisor"

    echo "* Installing OS requirements."
    PACKAGES="git python3-virtualenv python3-devel libvirt-devel glibc gcc nginx python3-lxml python3-libguestfs iproute-tc cyrus-sasl-md5 openldap-devel"
    install_packages

    set_hosts

    echo "* Configuring virtualenv."
    log "pip3 install virtualenv"
    install_webvirtcloud

    echo "* Configuring Nginx."
    configure_nginx

    echo "* Configuring Supervisor."
    log "pip3 install supervisor "
    configure_supervisor
    
    set_firewall 
    
    set_selinux

    restart_supervisor
    restart_nginx
    
  else
    echo "Unsupported RHEL Based OS($distro) version. Version found: $version"
    exit 1
  fi
  ;;
  uos)
  if [[ "$version" == "20" ]]; then
    # Install for uos 20
    tzone=\'$(timedatectl|grep "Time zone"| awk '{print $3}')\'

    echo "* Installing OS requirements."
    if test "${codename}" == "eagle"; then
      PACKAGES="git virtualenv python3-virtualenv python3-dev python3-lxml libvirt-dev zlib1g-dev libxslt1-dev nginx libsasl2-modules gcc pkg-config python3-guestfs uuid"
    else
      PACKAGES="git python3-virtualenv python3-devel python3-pip libvirt-devel glibc gcc nginx python3-lxml python3-libguestfs iproute-tc cyrus-sasl-md5"
    fi
    install_packages

    set_hosts

    install_webvirtcloud

    echo "* Configuring Nginx."
    configure_nginx

    echo "* Configuring Supervisor."
    log "pip install supervisor "
    configure_supervisor

    set_firewall

    set_selinux

    restart_supervisor
    restart_nginx
  fi
  ;;
  openEuler)
  if [[ "$version" == "20.03" ]]; then
    # Install for openEuler 20.03
    tzone=\'$(timedatectl|grep "Time zone"| awk '{print $3}')\'

    echo "* Installing OS requirements."
    PACKAGES="git python3-virtualenv python3-devel python3-pip libvirt-devel glibc gcc nginx python3-lxml python3-libguestfs iproute-tc cyrus-sasl-md5"
    install_packages

    set_hosts

    install_webvirtcloud

    echo "* Configuring Nginx."
    configure_nginx

    echo "* Configuring Supervisor."
    log "pip install supervisor "
    configure_supervisor

    set_firewall

    set_selinux

    restart_supervisor
    restart_nginx
  fi
  ;;
esac


echo ""
echo "  ***Open http://$fqdn to login to webvirtcloud.***"
echo ""
echo ""
echo "* Cleaning up..."
rm -f webvirtcloud.sh
rm -f install.sh
echo "* Finished!"
sleep 1
